#!/usr/bin/env python
# Create: 2018/10/26
__author__ = '749B'
"""selector 模块实现的异步IO的 HTTP 请求"""

import selectors
import socket
from bs4 import BeautifulSoup

url_list = [
    {'host': 'edu.51cto.com', 'port': 80, },
    {'host': 'www.baidu.com', 'port': 80, },
    {'host': 'www.python-requests.org', 'port': 80, 'url': '/en/master/'},
    {'host': 'open-falcon.org', 'port': 80, 'url': '/'},
    {'host': 'www.jetbrains.com', 'port': 80},
]


class HttpSocket(object):
    """接收一个实例化好的socket对象，在封装一些别的数据"""

    def __init__(self, sk, item):
        self.sk = sk
        self.item = item
        self.host = self.item.get('host')
        self.port = self.item.get('port', 80)
        self.method = self.item.get('method', 'GET')
        self.url = self.item.get('url', '/')
        self.body = self.item.get('body', '')
        self.callback = self.item.get('callback')
        self.buffer = []  # 请求的返回值记录在这里

    def fileno(self):
        """请求sockect对象的文件描述符，用于select监听"""
        return self.sk.fileno()

    def create_request_header(self):
        """创建请求信息"""
        request = '%s %s HTTP/1.0\r\nHost: %s\r\n\r\n%s' % (self.method.upper(), self.url, self.host, self.body)
        return request.encode('utf-8')

    def write(self, data):
        """把接收到的数据写入 self.buffer"""
        self.buffer.append(data)

    def finish(self):
        """接收完毕后执行的函数"""
        content = b''.join(self.buffer)
        head, body = content.split(b'\r\n\r\n', 1)
        print(head)
        print(len(body), body)
        soup = BeautifulSoup(body.decode(), features='html.parser')
        title = soup.find('title')
        print(title)


class AsyncRequest(object):

    def __init__(self):
        self.sel = selectors.DefaultSelector()

    def add_request(self, item):
        """创建连接请求"""
        host = item.get('host')
        port = item.get('port')
        client = socket.socket()
        client.setblocking(False)
        try:
            client.connect((host, port))
        except BlockingIOError as e:
            pass  # 至此，已经向服务器发出连接请求了
        hsk = HttpSocket(client, item)
        self.sel.register(hsk, selectors.EVENT_WRITE, self.connect)
        # 不同同时注册2个事件，下面的注册要等到连接建立之后执行
        # self.sel.register(sk, selectors.EVENT_READ, self.accept)

    def connect(self, hsk, mask):
        """建立连接后的回调函数
        发送请求，然后注册 EVENT_READ 事件
        """
        print("连接成功:", hsk.item)
        content = hsk.create_request_header()
        print("发送请求:", content)
        hsk.sk.sendall(content)
        self.sel.modify(hsk, selectors.EVENT_READ, self.accept)

    def accept(self, hsk, mask):
        """接收请求返回的内容"""
        # print("返回信息:", hsk.item)
        data = hsk.sk.recv(1024)
        if data:
            hsk.write(data)
        else:
            print("接收完毕", hsk.item)
            hsk.finish()
            self.sel.unregister(hsk)

    def run(self):
        """主函数"""
        while self.sel.get_map():
            events = self.sel.select()
            for key, mask in events:
                callback = key.data  # key.data就是sel.register里的第三个参数
                callback(key.fileobj, mask)  # key.fileobj就是sel.register里第一次参数


if __name__ == '__main__':
    obj = AsyncRequest()
    for url_dic in url_list:
        obj.add_request(url_dic)
    obj.run()

